﻿using System.Collections;
using System.Diagnostics.Contracts;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace YaTools.Yaml.UnitTests
{
    internal static class DeepAssert
    {
        public static void AreEqual(object expected, object actual)
        {
            var seenPairs = new Hashtable();
            AreEqual(expected, actual, " at ", seenPairs);
        }

        private static string Enquote(object o)
        {
            Contract.Requires(o != null);
            if (o is string) return string.Format("\"{0}\"", o);
            return string.Concat("{", o.ToString(), "}");
        }

        private static void AreEqual(object expected, object actual, string path, Hashtable seenPairs)
        {
            if (expected == null || actual == null)
            {
                Assert.AreEqual(expected, actual, path);
                return;
            }

            if (expected.Equals(actual)) return;

            Assert.AreEqual(expected.GetType(), actual.GetType(), "Types differ" + path);

            if (seenPairs.ContainsKey(expected))
            {
                Assert.AreEqual(seenPairs[expected], actual);
                return;
            }
            seenPairs.Add(expected, actual);

            var expectedCollection = expected as ICollection;
            var actualCollection = actual as ICollection;

            if (expectedCollection != null)
            {
                Assert.AreEqual(expectedCollection.Count, actualCollection.Count, "Counts differ" + path);

                var expectedDictionary = expected as IDictionary;
                var actualDictionary = actual as IDictionary;

                if (expectedDictionary != null)
                {
                    foreach (object key in expectedDictionary.Keys)
                    {
                        Contract.Assume(key != null);
                        AreEqual(expectedDictionary[key], actualDictionary[key],
                                 string.Format("{0}{1}", path, Enquote(key)), seenPairs);
                    }
                    return;
                }

                IEnumerator expectedEnum = expectedCollection.GetEnumerator();
                IEnumerator actualEnum = actualCollection.GetEnumerator();
                for (int i = 0; expectedEnum.MoveNext() && actualEnum.MoveNext(); i++)
                {
                    AreEqual(expectedEnum.Current, actualEnum.Current,
                             string.Format("{0}[{1}]", path, i), seenPairs);
                }
                return;
            }
            Assert.AreEqual(expected, actual);
        }
    }
}